`timescale 1ns/1ps
`default_nettype none

// ===== 选择 IMEM 储存颗粒度 =====
// 1) 如果 imem 是 reg [31:0] mem[...] (32-bit 数组)，保留下一行：
`define IMEM_WORDS    // <- 如果你的 IMEM 是 8-bit 数组，请把这一行注释掉
// 2) 如果 imem 是 reg [7:0]  mem[...] (8-bit  数组)，请注释掉上行（不要定义 IMEM_WORDS）

module tb_dmem_smoke;

  // ---------------- Clock / Reset ----------------
  reg clk = 1'b0; reg rstn = 1'b0;
  always #5 clk = ~clk;   // 100MHz

  task wait_cycles(input integer n); integer i; begin
    for (i=0;i<n;i=i+1) @(posedge clk);
  end endtask

  // ---------------- DUT ----------------
  cpu_top dut(.clk(clk), .rstn(rstn));

  // ---------------- Program file ----------------
  // 32-bit 指令版 / 8-bit 字节版各一份；根据 IMEM_WORDS 选择其一
  reg [1023:0] PROG_WORD_HEX = "prog_word.hex"; // 每行一个 32-bit 指令
  reg [1023:0] PROG_BYTE_HEX = "prog_byte.hex"; // 每个 token 1 字节（小端）

  integer MAX_CYCLES = 20000;

  // ---------------- Backdoor peek DMEM ----------------
  function [31:0] peek32(input [31:0] addr);
    reg [7:0] b0,b1,b2,b3;
  begin
`ifdef DMEM_BRAM
    b0 = dut.u_dmem.ram[addr+0];
    b1 = dut.u_dmem.ram[addr+1];
    b2 = dut.u_dmem.ram[addr+2];
    b3 = dut.u_dmem.ram[addr+3];
`else
    b0 = dut.u_dmem.mem[addr+0];
    b1 = dut.u_dmem.mem[addr+1];
    b2 = dut.u_dmem.mem[addr+2];
    b3 = dut.u_dmem.mem[addr+3];
`endif
    peek32 = {b3,b2,b1,b0}; // little-endian
  end endfunction

  // ---------------- Wave ----------------
  initial begin
    $dumpfile("tb_dmem_smoke.vcd");
    $dumpvars(0, tb_dmem_smoke);
  end

  // ---------------- Load program ----------------
  integer k;
  reg [31:0] w;
  initial begin
`ifdef IMEM_WORDS
    $display("TB: loading (32-bit) %0s into dut.u_imem.mem ...", PROG_WORD_HEX);
    $readmemh(PROG_WORD_HEX, dut.u_imem.mem);
`else
    $display("TB: loading (8-bit)  %0s into dut.u_imem.mem ...", PROG_BYTE_HEX);
    $readmemh(PROG_BYTE_HEX, dut.u_imem.mem);
`endif

    // 打印 IMEM 前 8 个 32-bit 指令（无论 IMEM 是 8-bit 还是 32-bit）
    $display("IMEM[0..7] words:");
    for (k=0;k<8;k=k+1) begin
`ifdef IMEM_WORDS
      w = dut.u_imem.mem[k];
`else
      w = {dut.u_imem.mem[k*4+3], dut.u_imem.mem[k*4+2],
           dut.u_imem.mem[k*4+1], dut.u_imem.mem[k*4+0]};
`endif
      $display("  [%0d]=0x%08x", k, w);
    end

    // 释放复位
    wait_cycles(2);
    rstn = 1'b1;
  end

  // ---------------- Heartbeat & timeout ----------------
  integer cyc=0;
  always @(posedge clk) if (rstn) begin
    cyc <= cyc + 1;
    if (cyc%1000==0) $display("[HB %0d] IF.pc=0x%08x", cyc, dut.if_pc);
    if (cyc==MAX_CYCLES) begin
      $display("[TIMEOUT] end.");
      $finish;
    end
  end

  // ---------------- Instrumentation ----------------
  // 1) ID 级：看到 load/store 就打印
  always @(posedge clk) if (rstn) begin
    if (dut.c_memwrite)
      $display("[ID] STORE seen @pc=0x%08x instr=0x%08x rs1=%0d rs2=%0d rd=%0d",
        dut.id_pc, dut.id_instr, dut.rs1, dut.rs2, dut.rd);
    if (dut.c_memread)
      $display("[ID] LOAD  seen @pc=0x%08x instr=0x%08x rs1=%0d rd=%0d",
        dut.id_pc, dut.id_instr, dut.rs1, dut.rd);
  end

  // 2) EX 级：地址计算
  always @(posedge clk) if (rstn) begin
    if (dut.ex_memwrite || dut.ex_memread)
      $display("[EX] addr=0x%08x a=0x%08x b=0x%08x imm=0x%08x",
        dut.ex_alu_y, dut.alu_a_pre, dut.rdata2_fwd, dut.ex_imm);
  end

  // 3) MEM 级：真正打到 DMEM 口的信号
  wire [31:0] mon_addr  = dut.u_dmem.addr;
  wire [31:0] mon_wdata = dut.u_dmem.wdata;
  wire [3:0]  mon_we    = dut.u_dmem.byte_we;

  integer wr_cnt=0;
  always @(posedge clk) if (rstn) begin
    if (mon_we != 4'b0000) begin
      $display("[DMEM-W %0d] addr=0x%08x wdata=0x%08x we=%b", wr_cnt, mon_addr, mon_wdata, mon_we);
      wr_cnt <= wr_cnt + 1;
    end
  end

  // ---------------- Final checks ----------------
  initial begin
    // 跑一会儿（足够完成 LUI/ADDI/SW/LW）
    wait_cycles(4000);

    // x1 应该是 0x00003008
    if (dut.u_rf.regs[1] !== 32'h00003008)
      $fatal(1, "x1 exp=0x00003008, got=0x%08x", dut.u_rf.regs[1]);

    // DMEM[0x3008] 应为 0x11223344
    if (peek32(32'h00003008) !== 32'h11223344)
      $fatal(1, "[FAIL] DMEM[0x3008] exp=0x11223344, got=0x%08x", peek32(32'h00003008));

    // x3 也应读回 0x11223344
    if (dut.u_rf.regs[3] !== 32'h11223344)
      $fatal(1, "x3 exp=0x11223344, got=0x%08x", dut.u_rf.regs[3]);

    $display("[PASS] DMEM smoke OK.");
    $finish;
  end

endmodule

`default_nettype wire
